package com.hjlsoft.aftp.ins.handler;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;

import com.hjlsoft.aftp.config.AftpConfig;
import com.hjlsoft.aftp.data.DataHandle;
import com.hjlsoft.aftp.data.DataMeta;
import com.hjlsoft.aftp.data.DataMetaStatusEnum;
import com.hjlsoft.aftp.data.DataStoreService;
import com.hjlsoft.aftp.ins.AftpResultCode;
import com.hjlsoft.aftp.network.Request;
import com.hjlsoft.aftp.network.Response;
import com.hjlsoft.aftp.token.Token;
import com.hjlsoft.aftp.token.TokenManager;

public class UploadInsHandle extends BaseInsHandle {

	private TokenManager tokenManager;
	
	private static final int BUF_SIZE = 1024 * 4;

	private DataStoreService dataStoreService;

	@Override
	public void onInit(AftpConfig config, Map<String, Object> services) {
		tokenManager = (TokenManager)services.get("tokenManager");
		dataStoreService = (DataStoreService)services.get("dataStoreService");
	}

	public void execute(Request request, Response response) throws IOException {
		if (!assertLogin(request, response)) {
			return;
		}
		List<String> params = request.getParamList();
		if (params == null || params.size() < 2) {
			response.writeResultCodeEnd(AftpResultCode.INVLIDATE_INS_PARAM);
			return;
		}
		String tokenId = params.get(0);
		String lengthString = params.get(1);
		long len = 0;
		try {
			len = Long.parseLong(lengthString);
		} catch (Exception e) {
			response.writeResultCodeEnd(AftpResultCode.INVLIDATE_INS_PARAM);
			return;
		}
		Token token = tokenManager.get(tokenId);
		if (token == null) {
			response.writeResultCodeEnd(AftpResultCode.INVALIDATE_TOKEN);
			return;
		}
		String username = this.getLoginUsername(request);
		if (!username.equals(token.getUsername())) {
			response.writeResultCodeEnd(AftpResultCode.TOKEN_NOT_BELONG_USER);
			return;
		}
		
		DataMeta dataMeta = dataStoreService.getDataMeta(tokenId);
		if (log.isInfoEnabled()) {
			log.info("last dataMeta:" + dataMeta);
		}
		if (dataMeta != null && dataMeta.getStatus() == DataMetaStatusEnum.COMMITTED.getValue()) {
			response.writeResultCodeEnd(AftpResultCode.DATA_HAS_BEEN_COMMITTED);
			return;
		}
		if (dataMeta == null) {
			dataMeta = new DataMeta();
			dataMeta.setCreateTime(System.currentTimeMillis());
			dataMeta.setId(tokenId);
			dataMeta.setPosition(0);
			dataMeta.setTotalSize(len);
		}
		long needLen = len - dataMeta.getPosition();
		if(log.isInfoEnabled()) {
			log.info("starting upload data, tokenId:" + tokenId + ", need len:" + needLen);
		}
		DataHandle dh = dataStoreService.create(token, dataMeta);
		try {
			processBody(request, response, dh, needLen);
		} finally {
			if (dh != null) {
				dh.close();
			}
		}
		
		if (log.isInfoEnabled()) {
			log.info("upload data finish: " + dh.getName());
		}
		
		response.writeResultCodeEnd(AftpResultCode.SUCCESS);
	}

	private void processBody(Request request, Response response, DataHandle dh, long needLength) throws IOException {
		long readLen = 0;
		ByteBuffer dst = ByteBuffer.allocate(BUF_SIZE);  
		
		while (true) {
			dst.clear();
			int len = request.readBodyFromRemain(dst);
			if (len <= 0) {
				break;
			}
			readLen += len;
		/*	if (log.isDebugEnabled()) {
				log.debug("readLen:" + readLen + ", bodyLength:" + bodyLength);
			}*/
			dst.flip();
			dh.write(dst);
			
			if (readLen >= needLength) {
				return;
			}
		}
		
		while (true) {
			synchronized (request.getBodyCanReadLock()) {
				if (!request.isBodyCanRead()) {
					try {
					/*	if (log.isDebugEnabled()) {
							log.debug("waiting for read...");
						}*/
						request.getBodyCanReadLock().wait();
					} catch (InterruptedException e) {
						Thread.currentThread().interrupt();
					}
				}
				if (readLen >= needLength) {
					return;
				}
				
				dst.clear();
				int readBufLen = (int)Math.min((long)BUF_SIZE, needLength - readLen);
				dst.limit(readBufLen);
				
				
				int len = request.readBody(dst);
				if (len < 0) {
					log.warn("connection disconnect.");
					return;
				}
				readLen += len;
				/*if (log.isDebugEnabled()) {
					log.debug("readLen:" + readLen + ", bodyLength:" + bodyLength);
				}*/
				dst.flip();
				
				dh.write(dst);
				
				if (readLen >= needLength) {
					return;
				}
				
				request.setBodyCanRead(false);
			}
		}
		
		
	}
	
	public String getIns() {
		return "upload";
	}

}
